﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.Types;
using IWMAG.Scripting.Interpreter;
using IWMAG.Scripting.AST.TerminalExpressions;
using IWMAG.Scripting.Compiler.UserInfo;

namespace IWMAG.Scripting.AST.Operators {
    internal abstract class UnaryOperator : CodeExpression {
        public CodeExpression GHS;

        public UnaryOperator(Compiler.Lexeme l, CodeExpression ghs)
            : base(l) {
            GHS = ghs;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            GHS.Verify(sym, error);
        }
    }

    internal abstract class SameTypedUnaryOperator : UnaryOperator {
        protected CodeType Type;

        public SameTypedUnaryOperator(Compiler.Lexeme l, CodeExpression ghs, CodeType t) : base(l, ghs) {
            Type = t;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);
            if (GHS.TypeOf() != Type) {
                SendError(error, Rules.UnaryOperatorDoesNotAcceptIncompatibleType,Type.ToString());
            }
        }

        public override CodeType TypeOf() {
            return Type;
        }
    }

    internal class PreIncOperator : SameTypedUnaryOperator {
        public PreIncOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            if (GHS is VariableExpression) {
                RunValue v = sym.Variables[(GHS as VariableExpression).Name];
                v.Value = v.IntValue + 1;
                val.ReferencedValue.Value = v.IntValue;
            } else {
                throw new NotImplementedException();
            }
            yield break;
        }
    }

    internal class PostIncOperator : SameTypedUnaryOperator {
        public PostIncOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            if (GHS is VariableExpression) {
                RunValue v = sym.Variables[(GHS as VariableExpression).Name];
                v.Value = v.IntValue + 1;
                val.ReferencedValue.Value = v.IntValue - 1;
            } else {
                throw new NotImplementedException();
            }
            yield break;
        }
    }

    internal class PreDecOperator : SameTypedUnaryOperator {
        public PreDecOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            if (GHS is VariableExpression) {
                RunValue v = sym.Variables[(GHS as VariableExpression).Name];
                v.Value = v.IntValue - 1;
                val.ReferencedValue.Value = v.IntValue;
            } else {
                throw new NotImplementedException();
            }
            yield break;
        }
    }

    internal class PostDecOperator : SameTypedUnaryOperator {
        public PostDecOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            if (GHS is VariableExpression) {
                RunValue v = sym.Variables[(GHS as VariableExpression).Name];
                v.Value = v.IntValue - 1;
                val.ReferencedValue.Value = v.IntValue - 1;
            } else {
                throw new NotImplementedException();
            }
            yield break;
        }
    }

    internal class BitNotOperator : SameTypedUnaryOperator {
        public BitNotOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> temp = RunValue.NoValue;
            foreach (Continuation wait in GHS.ValueOf(engine, sym, temp)) yield return wait;
            val.ReferencedValue.Value = ~temp.ReferencedValue.IntValue;
        }
    }

    internal class NegOperator : UnaryOperator {
        public NegOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs) { }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            GHS.Verify(sym, error);

            if (GHS.TypeOf() != CodeType.Int && GHS.TypeOf() != CodeType.Real) {
                SendError(error, "Unary negative expects a numeric type");
            }
            base.Verify(sym, error);
        }

        public override CodeType TypeOf() {
            return (GHS.TypeOf() == CodeType.Int) ? CodeType.Int : CodeType.Real;
        }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> temp = RunValue.NoValue;
            foreach (Continuation wait in GHS.ValueOf(engine, sym, temp)) yield return wait;
            if (temp.ReferencedValue.Value is int) {
                val.ReferencedValue.Value = -temp.ReferencedValue.IntValue;
            } else {
                val.ReferencedValue.Value = -temp.ReferencedValue.RealValue;
            }
        }
    }

    internal class NotOperator : SameTypedUnaryOperator {
        public NotOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs, CodeType.Bool) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> temp = RunValue.NoValue;
            foreach (Continuation wait in GHS.ValueOf(engine, sym, temp)) yield return wait;
            val.ReferencedValue.Value = !temp.ReferencedValue.BoolValue;
        }
    }

    internal class ConstOperator : UnaryOperator {
        protected RunValue Cached = null;

        public ConstOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs) { }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);
        }

        public override CodeType TypeOf() {
            return GHS.TypeOf();
        }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            if (Cached == null) {
                foreach (Continuation wait in GHS.ValueOf(engine, sym, val)) yield return wait;
                Cached = val.ReferencedValue;
            } else {
                val.ReferencedValue = Cached;
            }
            yield break;
        }
    }

    internal class NextIndexOperator : UnaryOperator {
        public NextIndexOperator(Compiler.Lexeme l, CodeExpression ghs) : base(l, ghs) {
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            if (!sym.Lookahead.Contains(GetType())) {
                SendError(error, "Array append operator has no meaning as a non-lvalue");
            }

            GHS.Verify(sym, error);
        }

        public override CodeType TypeOf() {
            return (GHS.TypeOf() as ArrayType).BaseType;
        }
    }
}
